package com.free.utils.crypt;

import com.free.exception.SystemRuntimeException;
import com.free.utils.CharsetUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.*;
import java.util.List;
import java.util.stream.Collectors;


/**
 * RSA签名,加解密处理核心文件，注意：密钥长度1024
 */
public class RSACrypt
{

    // 签名算法
    private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";

    // 加密算法RSA
    private static final String KEY_ALGORITHM = "RSA";
    
    // RSA最大加密明文大小
    private static final int MAX_ENCRYPT_BLOCK = 117;

    // RSA最大解密密文大小
    private static final int MAX_DECRYPT_BLOCK = 128;


    public static String encryptByModuluString(String content, String moduluBase64, String exponentStr, String charset)
    {
        try
        {
            byte[] modulusBytes = Base64.decodeBase64(moduluBase64);
            byte[] exponentBytes = Base64.decodeBase64(exponentStr);
            BigInteger modulus = new BigInteger(1, modulusBytes);
            BigInteger exponent = new BigInteger(1, exponentBytes);

            RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
            KeyFactory fact = KeyFactory.getInstance(KEY_ALGORITHM);
            PublicKey pubKey = fact.generatePublic(rsaPubKey);

            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);

            byte[] cipherData = cipher.doFinal(content.getBytes(charset));
            return Base64.encodeBase64String(cipherData);
        }
        catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException
                | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    public static String encryptByModuluString(String content, String moduluBase64, String exponentStr)
    {
        return encryptByModuluString(content, moduluBase64, exponentStr, CharsetUtils.UTF8);
    }

    public static String decryptByModuluString(String encryptedBase64Data, String moduluBase64, String delementBase64,
            String charset)
    {
        if (StringUtils.isEmpty(encryptedBase64Data))
        {
            return "";
        }
        
        try
        {
            byte[] expBytes = Base64.decodeBase64(delementBase64);
            byte[] modBytes = Base64.decodeBase64(moduluBase64);
            byte[] encrypted = Base64.decodeBase64(encryptedBase64Data);

            BigInteger modules = new BigInteger(1, modBytes);
            BigInteger exponent = new BigInteger(1, expBytes);

            KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);

            RSAPrivateKeySpec privSpec = new RSAPrivateKeySpec(modules, exponent);
            PrivateKey privKey = factory.generatePrivate(privSpec);
            cipher.init(Cipher.DECRYPT_MODE, privKey);
            byte[] decrypted = cipher.doFinal(encrypted);
            return new String(decrypted, charset);
        }
        catch (IOException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeySpecException
                | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    public static String decryptByModuluString(String encryptedBase64Data, String moduluBase64, String delementBase64)
    {
        return decryptByModuluString(encryptedBase64Data, moduluBase64, delementBase64, CharsetUtils.UTF8);
    }

    public static String sign(String text, File privateKeyfile, String charset)
    {
        try
        {
            byte[] keyBytes = Base64.decodeBase64(readKeyFromFile(privateKeyfile));
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(privateK);
            signature.update(text.getBytes(charset));
            byte[] result = signature.sign();
            return Base64.encodeBase64String(result);
        }
        catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException
                | IOException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    public static String sign(String text, File privateKeyfile)
    {
        return sign(text, privateKeyfile, CharsetUtils.UTF8);
    }

    public static boolean verify(String text, String sign, File publicKeyfile, String charset)
    {
        try
        {
            byte[] keyBytes = Base64.decodeBase64(readKeyFromFile(publicKeyfile));
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            PublicKey publicK = keyFactory.generatePublic(keySpec);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(publicK);
            signature.update(text.getBytes(charset));
            return signature.verify(Base64.decodeBase64(sign));
        }
        catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | SignatureException
                | IOException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    public static boolean verify(String text, String sign, File publicKeyfile)
    {
        return verify(text, sign, publicKeyfile, CharsetUtils.UTF8);
    }

    public static String decryptByPrivateKey(String encryptedBase64Data, File privateKeyfile, String charset)
    {
        try
        {
            byte[] keyBytes = Base64.decodeBase64(readKeyFromFile(privateKeyfile));
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, privateK);

            byte[] encryptedData = Base64.decodeBase64(encryptedBase64Data);
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0)
            {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK)
                {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                }
                else
                {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData, charset);
        }
        catch (NoSuchAlgorithmException | IOException | InvalidKeySpecException | NoSuchPaddingException
                | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    public static String decryptByPrivateKey(String encryptedBase64Data, File privateKeyfile)
    {
        return decryptByPrivateKey(encryptedBase64Data, privateKeyfile, CharsetUtils.UTF8);
    }

    public static String encryptByPublicKey(String content, String publicKey, String charset)
    {
        try
        {
            if (StringUtils.isEmpty(content))
            {
                return "";
            }
            
            byte[] data = content.getBytes(charset);
            byte[] keyBytes = Base64.decodeBase64(publicKey);
            X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            Key publicK = keyFactory.generatePublic(x509KeySpec);
            // 对数据加密
            Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
            cipher.init(Cipher.ENCRYPT_MODE, publicK);
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0)
            {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK)
                {
                    cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
                }
                else
                {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            return Base64.encodeBase64String(encryptedData);
        }
        catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException
                | IllegalBlockSizeException | BadPaddingException | IOException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    public static String encryptByPublicKey(String content, File publicKeyfile, String charset)
    {
        try
        {
            return encryptByPublicKey(content, readKeyFromFile(publicKeyfile), CharsetUtils.UTF8);
        }
        catch (IOException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    public static String encryptByPublicKey(String content, File publicKeyfile)
    {
        try
        {
            return encryptByPublicKey(content, readKeyFromFile(publicKeyfile), CharsetUtils.UTF8);
        }
        catch (IOException e)
        {
            throw new SystemRuntimeException("解密出错", e);
        }
    }

    private static String readKeyFromFile(File file) throws IOException
    {
        List<String> allLines = Files.readAllLines(file.toPath(), CharsetUtils.SYS_CHARSET);
        return allLines.stream().filter(line -> !"".equals(line) && !line.startsWith("--")).collect(Collectors.joining());
    }

    // TEST
    public static void main(String[] args)
    {
        String encryptStr = "abcd";
        encryptStr = RSACrypt
                .sign(encryptStr, new File("E:\\project\\jpt\\src\\mobileWeb\\src\\200006819658_encrypt_public.pem")); 
        encryptStr = RSACrypt.encryptByPublicKey("420624198206180050",
                new File("E:\\project\\jpt\\src\\mobileWeb\\src\\sinaSign\\200006819658_encrypt_public.pem"));
        System.out.println(encryptStr);
    }
    
}
